/* asciiboard.c contains code for all asciiboard engine functions.  This
   code is the user interface between the engine and the user.  There are
   several sets of functions:
   
   The asciiboard functions are required to use the engine in an application.
   This includes functions such as engine initialisation.  There are also a
   set of useful debugging programs.  There is a set of functions useful if 
   you want to write a text processing function to add to the engine. There 
   is a set of text processing functions which are useful for adding to the 
   processor.  Finally, there is a small set of useful DSP for applying to the 
   output of the engine.  Note that when recording a performance, only the 
   characters are recorded, so the idea is to do as little DSP on the output 
   as possible! 
   
   Copyright (C) 2014 James Boyle

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
#include <stddef.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>
#include "asciiboard.h"
#include "synth.h"
#include "processor.h"
#include "asciiboard_const.h"

/* global data for asciiboard */
static void (*save_func)(const char);
static SYNTH ascii_synth;
static PROCESSOR text_processor;

/* forward declaration of static functions */
static void save_float(const float f);

/* --------------------------------------------------------------------------*/
/* functions required to use the engine in a program */

/* asciiboard_init() should be called before using the asciiboard engine in a 
  program. */
void asciiboard_init(void (*sf)(const char), float *out_buffer,
                     const int buffer_size,
                     void (*af)(const float *, const int, void *),
                     void *af_args,
					 int (*tf)(void))
{
	save_func = sf;
	synth_init(&ascii_synth, out_buffer, buffer_size, af, af_args);
	processor_init(&text_processor, NULL);
}

/* asciiboard() takes a single character as an input, and updates the state
   of the synthesiser.  If a "next period" code is received, the synth
   produces a period of audio. */
int asciiboard(const char ch)
{
	return process(&text_processor, ch);
}

/* asciiboard_add_processing_func() adds a given function to the list of
   processor functions.  An error is returned is the insertion failed,
   NO_ERROR (0) returned otherwise. */
int asciiboard_add_processing_func(int (*f)(const char), const int priority)
{
	return processor_add_func(&text_processor, f, priority);
}

/* asciiboard_finish() should be called when the asciiboard engine is no
   longer needed. */
void asciiboard_finish(void)
{
	processor_finish(&text_processor);
	save_func = NULL;
}

/* save_float() converts a float to its string representation, which is
   then saved using the user defined save function.  A precision of 7 is
   used, considering floating point numbers typically support ~6-7 bits
   of precision.  Decimal representation is used e.g. 0.0161. */
static void save_float(const float f)
{
	int i;
	char buffer[FLOAT_BUFFER_SIZE];

	if (save_func == NULL)
		return;

	/* the buffer size is very generous for a single precision float ~7 decimal
	   places, however snprintf() is safer, if supported */
#ifdef C99_SUPPORTED
	snprintf(buffer, FLOAT_BUFFER_SIZE, "%.7f", f, );
#else
	sprintf(buffer, "%.7f", f);
#endif

	for (i = 0; i < FLOAT_BUFFER_SIZE; i++)
	{
		if (buffer[i] == '\0')
			break;
		save_func(buffer[i]);
	}
}

/* --------------------------------------------------------------------------- */
/* functions useful for debugging the engine */

/* asciiboard_benchmark() runs the engine for a given number of periods, simply
   playing every note simulataneously and generating a given number of periods.
   It then calculates useful values like the time taken to generate a single
   period.  It works out if the current configuration could be run in
   realtime. */
void asciiboard_benchmark(const int iterations)
{
	clock_t start, end;
	const double period_dur = (double) PERIOD_SAMPLES / SRATE;
	const double audio_dur = period_dur * iterations;
	int clock_cycles, i;
	double elapsed_time, period_gen, sample_gen;
	float audio_buffer[PERIOD_SAMPLES];

	printf("Benchmark: Starting benchmark.\n");
	asciiboard_init(NULL, audio_buffer, PERIOD_SAMPLES, NULL, NULL, NULL);
	/* just have throughput */
	asciiboard_add_processing_func(ascii_through, HIGH);

	printf("Benchmark: Running benchmark...\n");
	/* play all tones at once */
	for (i = 0; i < MAX_POLYPHONY; i++)
		asciiboard(i+'a');
	start = clock();
	for (i = 0; i < iterations; i++)
	{
		asciiboard(NEXT_CODE);
	}
	end = clock();
	printf("Benchmark: Benchmark finished.\n");
	/* calculate useful values */
	clock_cycles = end - start;
	printf("Benchmark: %lfs of audio generated.\n", audio_dur);
	elapsed_time = (double) clock_cycles / CLOCKS_PER_SEC;
	printf("Elapsed Time: %es.\n", elapsed_time);
	period_gen = elapsed_time / iterations;
	printf("Time to generate one period (equiv to %lfs of audio): %es.\n",
	       period_dur, period_gen);
	sample_gen = period_gen / PERIOD_SAMPLES;
	printf("Time to generate one sample: %es\n", sample_gen);

	/* work out if the engine could run in realtime on the current set-up */
	if (period_gen < (1.0 / CRATE))
	{
		printf("Benchmark: Frames generated faster than the control ");
		printf("rate - system could be run in realtime.\n");
	}
	else
	{
		printf("Benchmark: Frames generated slower than the control rate - ");
		printf("system can not be run in realtime.\n");
	}

	asciiboard_finish();
}

/* asciiboard_tone_debug() makes it easier to debug the synthesiser tone
   management structure, simply printing the entire state of the tone
   structure to the console. */
void asciiboard_tone_debug(void)
{
#ifdef DEBUG
	synth_tone_debug(&ascii_synth.tone_data);
#else
	printf("Note: Debugging not enabled - use the preprocessor definition ");
	printf("DEBUG=1 to enable");
#endif
}

/* asciiboard_stress_test() properly tests the engine by generating a
   given number of periods of random audio.  Useful for testing your
   custom functions for the processor. */
void asciiboard_stress_test(const int max_iterations)
{
	int i;
	char ch;

	printf("ASCIIBoard Stress Test: Randomly generate %d input codes\n",
	       max_iterations);
	for (i = 0; i < max_iterations; i++)
	{
		/* generate a tone character or next frame character */
		ch = NEXT_CODE + (rand() % 28);
		/* ignore the silence code as it tends to just end up having no output */
		if (ch != SILENCE_CODE)
			asciiboard(ch);
	}

	asciiboard_tone_debug();
}

/* --------------------------------------------------------------------------*/
/* functions to help interface with the ASCIIBoard engine, allowing you to
   write your own text processing functions more easily */

/* convert_char_to_tone() takes an ASCII character and returns its tone value,
   where 'a' is tone 0 and 'z' is tone 25.  Invalid characters are rounded up
   or down to the closest value. */
const int convert_char_to_tone(const char ch)
{
	if (ch <= 'a')
		return 0;
	else if( ch >= 'z')
		return TONE_COUNT-1;
	else
		return ch - 'a';
}

/* is_tone_code() returns whether the given code is a tone (1 if it is). */
const int is_tone_code(const char ch)
{
	return (ch >= 'a' && ch <= 'z');
}

/* is_streaming() returns if the synthesiser is currently streaming data,
   useful for realtime applications. */
int streaming_data(void)
{
	return (ascii_synth.rw != READ);
}

/* envelopes_synced() returns whether the envelopes are currently synced or
   not. */
int envelopes_synced(void)
{
	return ascii_synth.tone_data.envelopes_synced;
}

/* active_tone_count() returns the number of currently sounding tones, which
  may range from 0 to MAX_POLYPHONY. */
int sounding_tone_count(void)
{
	return ascii_synth.tone_data.sounding_count;
}

/* transmit_ch() should be used whenever sending a character to the synth,
   if save is enabled the character is saved, and then processed by the
   synth.  When writing text processing functions to be used in the
   processor, you should use this function to transmit characters. */
void transmit_ch(const char ch)
{
	int error;

	/* if saving is enabled, indicated with a valid save function pointer, then
	   save all output via the user supplied save function */
	if (save_func != NULL)
		save_func(ch);

	/* pass the character to the synthesiser */
	error = synth_process_ch(&ascii_synth, ch);

#ifdef DEBUG
	if (error)
		asciiboard_error(error);
#else
	(void) error; /* to stop compiler warning */
#endif
}

/* set_wave_table() lets you directly change the wave tables of the synth,
   which should contain a single period of a waveform.  Note it is assumed
   that the array is of size WAVE_TABLE_SIZE. */
int set_wave_table(const float *wave, const int size, const int chan)
{
	int i, code;

	if (size < WAVE_SIZE)
		return INVALID_ARG;

	if (chan == 0)
		code = RW_WAVE_ONE_CODE;
	else
		code = RW_WAVE_TWO_CODE;

	if (save_func != NULL)
	{
		/* save the new waveform as a stream of numbers */
		save_func(code);
		for (i = 0; i < WAVE_SIZE-1; i++)
		{
			save_float(wave[i]);
			save_func(NEXT_VAL_CODE);
		}
		/* write the last value without a separator */
		save_float(wave[i]);
		/* terminate the stream */
		save_func(code);
	}

	/* physically copy the data */
	synth_wave_copy(&ascii_synth, wave, chan);

	if (size > WAVE_SIZE)
		return COPY_OVERRUN;
	else
		return NO_ERROR;
}

/* set_envelope_pts() lets you directly change the envelopes in the synth.
   This allows each the sound of each note to be shaped. */
int set_envelope_pts(const float *envelope, const int size, const int chan)
{
	int i, code;

	if (chan == 0)
		code = RW_ENVELOPE_ONE_CODE;
	else
		code = RW_ENVELOPE_TWO_CODE;

	/* save the new envelope as a stream of numbers */
	if (save_func != NULL)
	{
		save_func(code);
		for (i = 0; i < WAVE_SIZE-1; i++)
		{
			save_float(envelope[i]);
			save_func(NEXT_VAL_CODE);
		}
		/* write the last value without a separator */
		save_float(envelope[i]);
		/* terminate the stream */
		save_func(code);
	}

	return synth_envelope_copy(&ascii_synth, size, envelope, chan);
}

/* set_tone_map() lets you directly change the tone maps, allowing each
   tone to be associated with any frequency. */
int set_tone_map(const float *map, const int size, const int chan)
{
	int i, code;

	if (size < TONE_COUNT)
		return INVALID_ARG;

	if (chan == 0)
		code = RW_MAP_ONE_CODE;
	else
		code = RW_MAP_TWO_CODE;

	/* save the new tone map as a stream of numbers */
	if (save_func != NULL)
	{
		save_func(code);
		for (i = 0; i < WAVE_SIZE-1; i++)
		{
			save_float(map[i]);
			save_func(NEXT_VAL_CODE);
		}
		/* write the last value without a separator */
		save_float(map[i]);
		/* terminate the stream */
		save_func(code);
	}

	synth_map_copy(&ascii_synth, map, chan);

	if (size > TONE_COUNT)
		return COPY_OVERRUN;
	else
		return NO_ERROR;
}

/* asciiboard_set_audio_buffer() allows you to store the output of the synth in
   own buffer of a chosen size.  Note that no checks are made on the buffer
   size, or if the buffer is a valid array.*/
void asciiboard_set_audio_buffer(float *const new_buffer, const int buffer_size)
{
	ascii_synth.audio_buffer = new_buffer;
	ascii_synth.audio_buffer_size = buffer_size;
}

const float *read_wave_table(const int chan)
{
	return ascii_synth.wave_table[chan];
}

const float *read_envelope(const int chan, int *size)
{
	*size = ascii_synth.envelope_len[chan];
	return ascii_synth.envelope[chan];
}

const float *read_tone_map(const int chan)
{
	return ascii_synth.tone_map[chan];
}

/* --------------------------------------------------------------------------*/
/* a set of useful and example text processing functions */

/* ascii_through() transmits every character through the the synth, with no
   text processing. */
int ascii_through(const char ch)
{
	transmit_ch(ch);
	return NO_ERROR;
}

/* ascii_through_comments() simply transmits most characters straight to the
   synth, but allows the use of a comment character (/) which causes any
   enclosed characters to be ignored.  A newline ends a comment.  This may
   be useful for reading text file input. */
int ascii_through_comments(const char ch)
{
	static const char comment_code = '/';
	static int comment = FALSE;

	/* if the comment code (/) is encountered, toggle commenting on or off */
	if (ch == comment_code)
		comment = !comment;
	/* a new line always ends a comment */
	else if (ch == '\n')
		comment = FALSE;

	if (comment == FALSE)
		transmit_ch(ch);
	return NO_ERROR;
}

int ascii_through_sustain(const char ch)
{
	int update[TONE_COUNT], i, update_count = 0;

	/* tone codes should only be passed through ascii_sustain(), which manages
	   all playing tones */
	if (is_tone_code(ch))
	{
		update_count = sustain(ch, update);
		for (i = 0; i < update_count; i++)
			transmit_ch(update[i]);
	}
	/* the reset and sustain code should be sent to both ascii_sustain() */
	else if (ch == RESET_CODE || ch == SUSTAIN_CODE)
	{
		update_count = sustain(ch, update);
		for (i = 0; i < update_count; i++)
			transmit_ch(update[i]);
		transmit_ch(ch);
	}
	else /* pass any other characters through */
		transmit_ch(ch);

	return NO_ERROR;
}

int ascii_through_release(const char ch)
{
	int update[TONE_COUNT], i, update_count = 0;

	/* tone and release codes should only be passed to ascii_release(),
	   which manages all playing tones */
	if (is_tone_code(ch) || ch == LINEAR_DECAY_CODE || ch == EXP_DECAY_CODE)
	{
		update_count = tone_release(ch, update);
		for (i = 0; i < update_count; i++)
			transmit_ch(update[i]);
	}
	/* reset and the next period codes are needed by both */
	else if (ch == RESET_CODE || ch == NEXT_CODE)
	{
		update_count = tone_release(ch, update);
		for (i = 0; i < update_count; i++)
			transmit_ch(update[i]);
		transmit_ch(ch);
	}
	else /* pass any other characters through */
		transmit_ch(ch);

	return NO_ERROR;
}

/* ascii_vibrato() implements vibrato, using the second channel and FM to
   subtly vary the frequency of the first channel.  When 'V' is pressed,
   vibrato is enabled.  Note that this function requires channel 2. */
int ascii_vibrato(const char ch)
{
	float vibrato_env[VIBRATO_DELAY];
	float sine[WAVE_SIZE], tmp[TONE_COUNT];
	int i;

	if (ch == 'V')
	{
		/* initialise the second wave table to a sine wave (default) */
		for (i = 0; i < WAVE_SIZE; i++)
			sine[i] = default_wave[i];
		set_wave_table(sine, WAVE_SIZE, CH_TWO);

		/* set the second envelope to a short ramp up to a small constant
		   amplitude - we only want a small variation in pitch */
		ramp_gen(vibrato_env, 0.0f, VIBRATO_AMOUNT, VIBRATO_DELAY);
		set_envelope_pts(vibrato_env, VIBRATO_DELAY, CH_TWO);

		/* set the second tone map so that any tone generates a slow
			frequency, around 6Hz */
		for (i = 0; i < TONE_COUNT; i++)
			tmp[i] = VIBRATO_FREQ;
		set_tone_map(tmp, TONE_COUNT, CH_TWO);

		/* select FM mixing mode, so channel two slowly modulates the
			frequency of channel 1 i.e. vibrato */
		transmit_ch(FM_CODE);
	}

	return NO_ERROR;
}

int ascii_tremolo(const char ch)
{
	float env[WAVE_SIZE], tmp[TONE_COUNT];
	int i;
	const float tremolo_amount = TREMOLO_AMOUNT;

	if (ch == TREMOLO_CODE)
	{
		/* initialise the second wave table to a triangle wave (default) */
		triangle_gen(env, MAX_HARMONIC, WAVE_SIZE);
		set_wave_table(env, WAVE_SIZE, CH_TWO);

		/* set the tremolo to a constant defined amplitude */
		set_envelope_pts(&tremolo_amount, 1, CH_TWO);

		/* set the second tone map so that any tone generates a slow
			frequency, around 6Hz */
		for (i = 0; i < TONE_COUNT; i++)
			tmp[i] = TREMOLO_FREQ;
		set_tone_map(tmp, TONE_COUNT, CH_TWO);

		/* select FM mixing mode, so channel two slowly modulates the
			frequency of channel 1 i.e. vibrato */
		transmit_ch(AM_CODE);
	}

	return NO_ERROR;
}

/* sustain() ignores any key releases, sustaining any played tones until
   the sustain is released.  Only key presses and key releases are
   transmitted. */
int sustain(const char ch, int tones_to_transmit[TONE_COUNT])
{
	static int tones_sustained[TONE_COUNT];
	static int sustain = FALSE;
	int i, tone_code, tone_val, transmit_count = 0;

	if (ch == SUSTAIN_CODE)
	{
		if (sustain)
		{
			/* release all sustained tones */
			for (i = 0; i < TONE_COUNT; i++)
			{
				if (tones_sustained[i])
				{
					/* for any sustained tones, send a tone off message */
					tone_code = i + 'a';
					tones_to_transmit[i] = tone_code;
				}
			}
			transmit_count = i;
			sustain = FALSE;
		}
		else
			sustain = TRUE;
	}
	else if (is_tone_code(ch))
	{
		/* if sustain is on, hold onto every tone until sustain is released */
		if (sustain)
		{
			tone_val = convert_char_to_tone(ch);
			/* if this is a new active tone, transmit it */
			if (tones_sustained[tone_val] == FALSE)
			{
				tones_to_transmit[0] = ch;
				transmit_count = 1;
			}

			tones_sustained[tone_val] = TRUE;
		}
		else /* if sustain is off, simply toggle tones normally */
		{
			tones_to_transmit[0] = ch;
			transmit_count = 1;
		}
	}
	/* else ignore any other characters */

	return transmit_count;
}

/* tone_release() allows a tone to die down after it's released instead
   of turning off instantly.  A linear or exponential decay envelope may be
   selected.  ascii_through_release() causes note releases to start a counter, which
   times the decay.  The wave table of the second channel is used as the
   decay envelope, and the two tone maps are used to track which notes are
   held and released.   Note this should be used instead of ascii_through().
   This may be useful for implementing an ADSR envelope - Attack Decay Sustain
   Release.  The synth must have its envelopes synced to work properly. */
int tone_release(const char ch, int tones_to_transmit[TONE_COUNT])
{
	const static float decay_speed =
		DECAY_INCR * SRATE / WAVE_SIZE;
	static int release_pos[TONE_COUNT];
	static float release_env[WAVE_SIZE], decay_speeds[TONE_COUNT], sustain;
	static TONE_STATE tone_states[TONE_COUNT];
	static int update_synth = FALSE, init = FALSE, mode = LINEAR_DECAY;
	const float *env;
	char tone_code;
	int env_size, t, transmit_count = 0;
	float tmp;

	/* check for the special decay mode codes, allowing the user to select a
	   decay type (linear or exponential) */
	if (ch == LINEAR_DECAY_CODE)
	{
		mode = LINEAR_DECAY;
		init = FALSE;
	}
	else if (ch == EXP_DECAY_CODE)
	{
		mode = EXP_DECAY;
		init = FALSE;
	}

	/* if the function is not initialised */
	if (init == FALSE)
	{
		/* set up all tones so they are set as "released" ie off */
		for (t = 0; t < TONE_COUNT; t++)
		{
			decay_speeds[t] = 0.0f;
			tone_states[t] = TONE_NOT_ACTIVE;
			release_pos[t] = 0;
		}

		/* set the initial value of the decay to the final value of the
		   envelope */
		env = read_envelope(CH_ONE, &env_size);
		sustain = env[env_size-1];

		/* use the wave table for channel 2 to generate a decay envelope */
		if (mode == LINEAR_DECAY)
			/* generate a straight line, from the sustain level to 0 */
			ramp_gen(release_env, sustain, 0.0f, WAVE_SIZE);
		else
		{
			/* generate an exponential decay, from the sustain level to 0
			   (approximately) */
			exp_gen(release_env, sustain, WAVE_SIZE / 5.0f,
			        WAVE_SIZE);
		}
		set_wave_table(release_env, WAVE_SIZE, CH_TWO);
		tmp = 1.0f;
		set_envelope_pts(&tmp, 1, CH_TWO);

		update_synth = TRUE;
		init = TRUE;

		/* set the synth to AM mode */
		transmit_ch(AM_CODE);
	}

	if (ch == RESET_CODE)
		init = update_synth = FALSE;
	/* if a tone character has been sent */
	else if (is_tone_code(ch))
	{
		t = convert_char_to_tone(ch);
		/* if the note has just been pressed */
		if (tone_states[t] == TONE_NOT_ACTIVE)
		{
			/* set the tone to active (sustain) mode */
			tone_states[t] = TONE_ACTIVE;
			/* update the synthesiser with the new tone */
			tones_to_transmit[0] = ch;
			transmit_count = 1;
			update_synth = TRUE;
		}
		else if (tone_states[t] == TONE_RELEASE)
		{
			/* set the tone to active (sustain) mode */
			tone_states[t] = TONE_ACTIVE;
			/* turn off the release envelope */
			decay_speeds[t] = 0.0f;
			update_synth = TRUE;
			/* the tone doesn't need to be transmitted to the synth as at
			   it's playing continuously */
		}
		else /* if a tone character has been released */
		{
			/* set to release mode, the tone will now die down */
			tone_states[t] = TONE_RELEASE;

			/* if the envelopes aren't synced, or this is the last tone
			   sounding, the note will decay */
			if (!envelopes_synced() || sounding_tone_count() == 1)
			{
				release_pos[t] = 0;
				decay_speeds[t] = decay_speed;
				update_synth = TRUE;
				/* don't transmit the released tone straight away */
			}
			/* if the envelopes are synced, all but the last sounding tone
			   are instantly stopped */
			else
			{
				tones_to_transmit[0] = ch;
				transmit_count = 1;
			}

		}
	}
	else if (ch == NEXT_CODE)
	{
		for (t = 0; t < TONE_COUNT; t++)
		{
			/* if at the end of the decay envelope, turn the tone off */
			if (tone_states[t] == TONE_RELEASE)
			{
				if (release_pos[t] >= DECAY_SAMPLE_COUNT)
				{
					tone_code = t + 'a';
					/* transmit the tone code */
					tones_to_transmit[0] = tone_code;
					transmit_count = 1;
					/* turn off the decay */
					decay_speeds[t] = 0.0f;
					tone_states[t] = TONE_NOT_ACTIVE;
					update_synth = TRUE;
				}
				else
					release_pos[t]++;
			}
		}

		/* if the synth needs to be updated */
		if (update_synth)
		{
			set_tone_map(decay_speeds, TONE_COUNT, CH_TWO);
			/* flag the synth as being updated */
			update_synth = FALSE;
		}
	}
	/* else ignore any other characters */

	return transmit_count;
}

/* ascii_common_waveform() allows a user to load several common waveforms
   into the wave table.  The channel that you're writing to can be toggled
   using '~', by default it is channel 1. */
int ascii_common_waveform(const char ch)
{
	static int chan = CH_ONE;
	float new_table[WAVE_SIZE];

	switch (ch)
	{
	case RESET_CODE:
		chan = CH_ONE;
		break;
	case WAVEFORM_TOGGLE_CHANNEL_CODE:
		/* toggle the channel to write to */
		if (chan == CH_ONE)
			chan = CH_TWO;
		else
			chan = CH_ONE;
		break;
	case SQUARE_CODE:
		square_gen(new_table, MAX_HARMONIC, WAVE_SIZE);
		set_wave_table(new_table, WAVE_SIZE, chan);
		break;
	case TRIANGLE_CODE:
		triangle_gen(new_table, MAX_HARMONIC, WAVE_SIZE);
		set_wave_table(new_table, WAVE_SIZE, chan);
		break;
	case SAW_CODE:
		saw_gen(new_table, MAX_HARMONIC, WAVE_SIZE);
		set_wave_table(new_table, WAVE_SIZE, chan);
		break;
	case NOISE_CODE:
		noise_gen(new_table, WAVE_SIZE);
		set_wave_table(new_table, WAVE_SIZE, chan);
		break;
	}
	return NO_ERROR;
}

/* asciiboard_error() takes an error code and prints a relevant message to
   the console. */
void asciiboard_error(const int error)
{
	switch (error)
	{
	case INVALID_ARG:
		printf("Note: Invalid arguments passed to function.\n");
		break;
	case INSUFFICIENT_MEM:
		printf("Error: Insufficient memory - allocation failed.\n");
		break;
	case INVALID_DATA_STREAM:
		printf("Note: Invalid data stream - unexpected data or ");
		printf("incorrect terminating code.\n");
		break;
	case INVALID_AUDIO_BUFFER_SIZE:
		printf("Note: Invalid audio buffer size.\n");
		break;
	case INVALID_AUDIO_BUFFER:
		printf("Note: Invalid audio buffer - size is > 0 but buffer ");
		printf("is invalid.\n");
		break;
	case DATA_STREAM_OVERRUN:
		printf("Note: Data stream overrun - data stream terminated.\n");
		break;
	case VAL_BUFFER_OVERRUN:
		printf("Note: Value buffer overrun - extra numbers / characters ");
		printf("will be ignored.\n");
		break;
	case COPY_OVERRUN:
		printf("Note: Copy overrun - attempted to copy too many values, ");
		printf("extra values will be ignored.\n");
		break;
	}
}

/* --------------------------------------------------------------------------*/
/* a set of useful waveform / envelope generating functions */
/* square_gen() generates a square wave, using additive synthesis */
void square_gen(float *square, const int max_harmonic, const int size)
{
	int i, n, sample;
	float a_n, tmp;

	for (i = 0; i < WAVE_SIZE; i++)
		square[i] = 0.0f;

	/* use additive synthesis up to a maximum number of harmonics to avoid
	   aliasing */
	for (i = 1; i <= max_harmonic; i++)
	{
		/* the square wave contains only odd harmonics */
		n = 2*i - 1;
		/* calculate the Fourier series coefficient */
		a_n = 1.0f / n;
		tmp = (TWOPI * n) / size;
		for (sample = 0; sample < size; sample++)
			square[sample] += a_n * sinf(tmp * sample);
	}

	/* normalise the wave table to 1.0 */
	normalise(square, 1.0f, size);
}

/* triangle_gen() generates a triangle wave, using additive synthesis */
void triangle_gen(float *triangle, const int max_harmonic, const int size)
{
	int i, n, sample;
	float a_n, tmp;

	for (i = 0; i < WAVE_SIZE; i++)
		triangle[i] = 0.0f;

	/* use additive synthesis up to a maximum number of harmonics to avoid
	   aliasing */
	for (i = 1; i <= max_harmonic; i++)
	{
		/* the triangle wave contains only odd harmonics */
		n = 2*i - 1;
		/* calculate the Fourier series coefficient (1 / n^2) */
		a_n = 1.0f / (n*n);
		tmp = (TWOPI * n) / size;
		for (sample = 0; sample < size; sample++)
			triangle[sample] += a_n * sinf(tmp * sample);
	}

	/* normalise the wave table to 1.0 */
	normalise(triangle, 1.0f, size);
}

/* saw_gen() generates a saw wave (up), using additive synthesis */
void saw_gen(float *saw, const int max_harmonic, const int size)
{
	int n, sample;
	float a_n, tmp;

	for (n = 0; n < WAVE_SIZE; n++)
		saw[n] = 0.0f;

	/* use additive synthesis up to a maximum number of harmonics to avoid
	   aliasing */
	for (n = 1; n <= max_harmonic; n++)
	{
		/* calculate the Fourier series coefficient (1 / n) for all
		   harmonics */
		a_n = 1.0f / n;
		tmp = (TWOPI * n) / size;
		for (sample = 0; sample < size; sample++)
			saw[sample] += a_n * sinf(tmp * sample);
	}

	/* normalise the wave table to 1.0 */
	normalise(saw, 1.0f, size);
}

/* ramp_gen() generates a linear ramp from values initial to end, between
   index 0 and size-1. */
void ramp_gen(float *ramp, const float initial, const float end,
              const int size)
{
	int i;
	float curr = initial, grad = (end - initial) / (size-1);

	for (i = 0; i < size; i++, curr += grad)
		ramp[i] = curr;
}

/* exp_gen() generates an exponential decay, starting value an initial value
   and decaying to 0.  Note that the decay rate is equivalent to the decay
   constant ie the time taken to fall to 37% of the original value, given as
   an array index (max be fractional however). */
void exp_gen(float *exp_decay, const float initial,
             const float decay_rate, const int size)
{
	int i;
	static const float epsilon = 1.0E-6f;
	float tmp;

	/* check for special case of no decay time ie instant decay */
	if (decay_rate <= epsilon)
	{
		exp_decay[0] = initial;
		for (i = 1; i < size; i++)
			exp_decay[i] = 0.0f;
	}
	else
	{
		for (i = 0; i < size; i++)
		{
			tmp = (float) (-i) / (float) decay_rate;
			exp_decay[i] = expf(tmp);
			exp_decay[i] *= initial;
		}
	}
}

/* noise_gen() is a very basic noise generator, that simply uses the built
   in random number generator to generate the samples.  The built in random
   number generator isn't actually *that* random so to get anything
   resembling white noise, try sounding all tones with this waveform at once,
   or alternatively find a pregenerated table of Gaussian white noise. */
void noise_gen(float *noise, const int size)
{
	int i;
	float rand_sample;

	for (i = 0; i < size; i++)
	{
		rand_sample = (float) rand() / RAND_MAX - 0.5f;
		noise[i] = rand_sample * 2.0f;
	}
}

/* --------------------------------------------------------------------------*/
/*  a small set of useful DSP functions, to apply to the audio output in your
   processing functions */

/* amplify() scales an entire buffer by a given amplitude */
void amplify(float *const samples, const float amp, const int count)
{
	int i;
	for (i = 0; i < count; i++)
		samples[i] *= amp;
}

/* normalise() scales a buffer so the peak absolute value is the given
   value */
void normalise(float *const samples, const float norm, const int count)
{
	int i;
	float curr, amp, max = 0.0;

	/* process the array to find the largest absolute value */
	for (i = 0; i < count; i++)
	{
		curr = fabsf(samples[i]);
		if (curr > max)
			max = curr;
	}

	/* amplify the signal to normalise it to the given value */
	amp = fabsf(norm) / max;
	amplify(samples, amp, count);
}

/* low_pass() uses a 2nd order Butterworth filter, coefficients and algorithm
   taken from "The Audio Programming Book" by Richard Boulanger, Victor
   Lazzarini and Richard Boulanger and "Computer Music" by Charles Dodge.  Note it is
   assumed that the read and write buffers are both smaller, or the same size
   as the given count */
void low_pass(const float *const read, float *write, const float cut_off,
              const int count)
{
	static float root_two = 1.41421356237f;
	static float feedback[2], feedforward[2];
	static float  a_0, a_1, a_2, b_1, b_2;
	static float lambda, freq;
	int i;

	if (freq != cut_off)
	{
		freq = cut_off;
		lambda = 1.0f / tanf(M_PI * cut_off / SRATE);

		a_0 = 1.0f / (1.0f + root_two*lambda + lambda*lambda);
		a_1 = 2.0f * a_0;
		a_2 = a_0;

		b_1 = 2 * a_0 * (1.0f - lambda*lambda);
		b_2 = a_0 * (1.0f - root_two*lambda + lambda*lambda);
	}

	for (i = 0; i < count; i++)
	{
		/* feedforward part */
		write[i] = a_0 * read[i] + a_1 * feedforward[0] + a_2 * feedforward[1];
		/* negative feedback part */
		write[i] -= b_1 * feedback[0] + b_2 * feedback[1];

		feedback[1] = feedback[0];
		feedback[0] = write[i];
		feedforward[1] = feedforward[0];
		feedforward[0] = read[i];
	}
}
